#ifndef __tenacitas_tuple_unpacker__unpack__h__
#define __tenacitas_tuple_unpacker__unpack__h__



/** @file */


/** @todo */

// ==> C++ Headers
#include <cstddef>
#include <tuple>
#include <functional>
#include <utility>
#include <type_traits>
#include <iostream>

// ==> 3rds Headers

// ==> Our Headers
#include "unpack.h"

// ==> Namespaces
using namespace std;

// ==> Macro Constants

// ==> Macro Commands


/** ==> namespace of the organization */
namespace tenacitas {
    /** ==> namespace for the project */
    namespace tuple_unpacker {

        // ==> Typedefs 

        // ==> Pre-Declarations

        // ==> Attributes 

        // ==> Functions
        
        // ==> Classes


        /** Unpacks a tuple, and calls a method of a class, or a functor

            @tparam t_result is the result type of the method/functor called
            @tparam t_args are the types of the parameters to be passed to the
            methiod/functor
            
            @nosubgrouping 
        */
        template<typename t_result, typename ... t_args>
        class unpack {

        public:

            // ==> unpack - Public Internal Types

            /** Type of the tuple to be unpacked */
            typedef typename std::tuple<t_args...> tuple_type;

            /** Type of the result of the method/functor called*/
            typedef t_result result_type;

            /** Type of the corresponding <tt>std::function</tt> to the
                method/functor to be called */
            typedef typename std::function<result_type(t_args...)>function_type; 
            
            // ==> unpack - Public Constructors

            /** Destructor */
            ~unpack( ) {}

            unpack() {}

            // ==> unpack - Public Operators

            /** Unpacks a @p tuple_type object, and calls a method

                @tparam t_class_method is the type of the method that will be
                called

                @param p_tupple is the tuple where the parameters are packed
                @param p_method is a reference to the method that will be called
                @param p_obj is the object used to call @p p_method
             */
//            template<typename t_class_method>
            template<typename t_class, typename t_method>
            result_type operator()(tuple_type && p_tuple,
                                   t_method p_method,
                                   t_class & p_obj ) {  

                typedef bind_method<std::tuple_size<tuple_type>::value,
                                    t_method> bind_method_type;
            

                bind_method_type l_bind_method;
                
                function_type l_function = l_bind_method( p_method, p_obj );

                tu<tuple_size<tuple_type>::value, tuple_type, function_type> l_tu;
                return l_tu(std::move(p_tuple), std::move(l_function));
            }

        private:

            // ==> unpack - Private Internal Types


            /** */
            template <size_t size, typename t_tuple_, typename t_function_>
            class tu;

            
            /** */
            template <typename t_tuple_, typename t_function_>
            class tu<1, t_tuple_, t_function_> {
            public:
                typedef t_function_ function_type;
                typedef t_tuple_ tuple_type;
                typedef typename function_type::result_type result_type;

                result_type operator()(tuple_type && p_tuple,
                                       function_type && p_function) {

                    typedef tuple_field_traits<0, tuple_type> first_field; 

                    std::cout << "tu<1>" << std::endl;
                    
                    typename first_field::type l_first (
                        std::move(std::get<0>(p_tuple)) );


                    call_f<function_type, first_field::is_rvalue> l_call;
                    return l_call(std::move(p_function), std::move(l_first));
                    
                }
            }; 


            /** */
            template <typename t_tuple_, typename t_function_>
            class tu<0, t_tuple_, t_function_> {
            public:
                typedef t_function_ function_type;
                typedef t_tuple_ tuple_type;
                typedef typename function_type::result_type result_type;

                result_type operator()(tuple_type && p_tuple,
                                       function_type && p_function) {
                    return p_function();
                }
            }; 


            /** */
            template <size_t index, typename t_tuple__>
            struct tuple_field_traits {
                typedef t_tuple__ tuple_type;

                typedef typename std::tuple_element<index, tuple_type>::type t1;

                typedef typename std::remove_reference<t1>::type t2;    
    
                typedef typename std::remove_cv<t2>::type type;

                static const bool is_rvalue = std::is_rvalue_reference<t1>::value;
            };


            /** */
            template <typename t_function__, bool is_rvalue>
            class call_f;

            /** */
            template <typename t_function__>
            class call_f<t_function__,true> {
                typedef t_function__ function_type;
                typedef typename function_type::result_type result_type;

            public:
                template <typename t_type>
                result_type operator()(function_type && p_function,
                                       t_type && p_value) {

                    std::cout << "call_f with move" << std::endl;
                    return p_function(std::move(p_value));
                }
            };

            /** */
            template <typename t_function__>
            class call_f<t_function__,false> {
                typedef t_function__ function_type;
                typedef typename function_type::result_type result_type;

            public:
                template <typename t_type>
                result_type operator()(function_type && p_function,
                                       t_type && p_value) {

                    std::cout << "call_f wo move" << std::endl;                    
                    return p_function(p_value);
                }

            };

            template <typename>
            struct deduce_class;
            

            template <typename t_return, typename t_class>
            struct deduce_class<t_return t_class::*> {
                typedef t_class class_type;
                typedef t_return return_type;
            };

            /** */
            template <size_t num_args, typename t_class_method>
            struct bind_method;

            /** */
            template <typename t_class_method>
            struct bind_method<1,t_class_method> {
                function_type operator( )( t_class_method p_method,
                                           typename
                                           deduce_class<t_class_method>::class_type &
                                           p_obj ) {

                    return std::bind( p_method, std::ref( p_obj ),
                                      std::placeholders::_1 );
                }
            };
        };
    }
}

// =============================================================================
// I M P L E M E N T A T I O N
// =============================================================================

using namespace tenacitas::tuple_unpacker;


#endif
